using FluentValidation;
using FluentValidation.Results;
using Mediator;
using SharedKernel;

namespace Application.Behaviors;

internal sealed class ValidationPipelineBehavior<TRequest, TResponse>(
    IEnumerable<IValidator<TRequest>> validators)
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IMessage
    where TResponse : Result
{
    private async Task<ValidationFailure[]> ValidateAsync(TRequest request)
    {
        if (!validators.Any())
        {
            return [];
        }

        var context = new ValidationContext<TRequest>(request);

        ValidationResult[] validationResults = await Task.WhenAll(
            validators.Select(validator => validator.ValidateAsync(context)));

        ValidationFailure[] validationFailures = validationResults
            .Where(validationResult => !validationResult.IsValid)
            .SelectMany(validationResult => validationResult.Errors)
            .ToArray();

        return validationFailures;
    }

    private static TResponse CreateValidationResult<T>(ValidationFailure[] validationFailures)
        where T : Result
    {
        ValidationError validationError = CreateValidationError(validationFailures);

        if (typeof(T) == typeof(Result))
        {
            return (TResponse)Result.Failure(validationError);
        }
        
        object? validationResult = typeof(Result)
            .GetMethods()
            .First(m => m.Name == nameof(Result.Failure) && m.IsGenericMethodDefinition)
            .MakeGenericMethod(typeof(T).GenericTypeArguments[0])
            .Invoke(null, [validationError]);

        return (TResponse)validationResult!;
    }

    private static ValidationError CreateValidationError(ValidationFailure[] validationFailures) =>
        new(validationFailures.Select(f => new Error(f.ErrorCode, f.ErrorMessage, ErrorType.Validation)).ToArray());

    public async ValueTask<TResponse> Handle(TRequest message, MessageHandlerDelegate<TRequest, TResponse> next, CancellationToken cancellationToken)
    {
        ValidationFailure[] validationFailures = await ValidateAsync(message);

        if (validationFailures.Length == 0)
        {
            return await next(message, cancellationToken);
        }
        
        return CreateValidationResult<TResponse>(validationFailures);
    }
}
